﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Beaker.OpenCube.IO;
using Beaker.OpenCube.Metadata;
using Beaker.Services.FileSystem;
using Beaker.Services.Tasks;

#endregion

namespace Beaker.OpenCube.Dynamic
{
	/// <summary>
	/// Dynamicly processes any files needed to access the save games
	/// </summary>
	public sealed class DynamicGame
	{
		private readonly IFileSystemService _fileSystem;

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		public DynamicGame()
			: this( null )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DynamicGame"/> class.
		/// </summary>
		/// <param name="rootGameFolder">The root game folder, or <c>null</c> to use the default game path.</param>
		/// <remarks>
		/// Requires the following services:
		/// * <see cref="IFileSystemService"/> for accessing the files and folders of the game files.
		/// </remarks>
		public DynamicGame( IFolder rootGameFolder )
		{
			_fileSystem = FileSystemService.Default;

			if( rootGameFolder == null )
				rootGameFolder =
					_fileSystem
						.GetSpecialFolder( Environment.SpecialFolder.ApplicationData )
						.GetFolder( ".minecraft" );

			if( rootGameFolder == null )
				throw new DirectoryNotFoundException( "The default game folder is not found." );

			_rootGameFolder = rootGameFolder;
		}

		[ContractInvariantMethod]
		private void ContractInvariant()
		{
			Contract.Invariant( _rootGameFolder != null );
			Contract.Invariant( _fileSystem != null );
		}

		private readonly IFolder _rootGameFolder;

		/// <summary>
		/// Gets the root game folder the dynamic game reader will use.
		/// </summary>
		/// <value>
		/// The root game folder.
		/// </value>
		public IFolder RootGameFolder
		{
			get
			{
				Contract.Ensures( Contract.Result<IFolder>() != null );
				return _rootGameFolder;
			}
		}

		#region Profiles

		public Task<ProfilesMetadata> GetProfilesAsync()
		{
			Contract.Ensures( Contract.Result<Task<ProfilesMetadata>>() != null );
			return TaskService.Default.Execute<ProfilesMetadata>( GetProfiles );
		}

		public ProfilesMetadata GetProfiles()
		{
			IFile file = _rootGameFolder.GetFile( "launcher_profiles.json" );
			if( file != null )
				return Metadata.Metadata.Read<ProfilesMetadata>( file );

			return null;
		}

		public IObservable<IFolder> GetProfileLocationsAsync()
		{
			return TaskService.Default.Observe( GetProfileLocations );
		}

		/// <summary>
		/// Gets all the 'unique' profile locations.
		/// </summary>
		/// <returns>A sequence containing all the 'unique' profile locations.</returns>
		/// <remarks>If multiple profiles use the same location, only one location entry is returned.</remarks>
		public IEnumerable<IFolder> GetProfileLocations()
		{
			return
				GetProfiles()
					.Profiles
					.Select( p => p.Value.GameFolder ?? _rootGameFolder )
					.GroupBy( f => f.Name, StringComparer.OrdinalIgnoreCase )
					.Select( g => g.First() );
		}

		#endregion
		#region GameVersions

		/// <summary>
		/// Gets all the installed game versions
		/// </summary>
		/// <returns>An observable returning the game versions</returns>
		public IObservable<GameVersionMetadata> GetInstalledGameVersionsAsync()
		{
			Contract.Ensures( Contract.Result<IObservable<GameVersionMetadata>>() != null );
			return TaskService.Default.Observe( GetInstalledGameVersions );
		}

		/// <summary>
		/// Gets all the installed game versions
		/// </summary>
		/// <returns>An enumerable with all the game versions</returns>
		public IEnumerable<GameVersionMetadata> GetInstalledGameVersions()
		{
			Contract.Ensures( Contract.Result<IEnumerable<GameVersionMetadata>>() != null );

			IFolder versionsFolder = _rootGameFolder.GetFolder( "versions" );
			if( versionsFolder != null )
			{
				IEnumerable<IFolder> versionFolders = versionsFolder.GetFolders();
				foreach( IFolder versionFolder in versionFolders )
				{
					IFile json = versionFolder.GetFile( versionFolder.Name + ".json" );
					if( json != null )
					{
						var metadata = json.ReadMetadata<GameVersionMetadata>();
						metadata.Folder = versionFolder;
						yield return metadata;
					}
				}
			}
		}

		#endregion
		#region TexturePack

		/// <summary>
		/// Returns the texture-pack file names found in the texture-pack folder.
		/// </summary>
		/// <returns>Sequence of all texture-pack file names found in the texture-pack folder.</returns>
		public IEnumerable<IFile> GetTexturePackFiles()
		{
			Contract.Ensures( Contract.Result<IEnumerable<IFile>>() != null );

			IFolder textPackFolder = _rootGameFolder.GetFolder( "texturepacks" );
			IEnumerable<IFile> zipFiles = textPackFolder.GetFiles().Where( f => f.Name.EndsWith( ".zip" ) );

			return
				new[] { GetDefaultTexturePackFile() }
					.Concat( zipFiles );
		}

		/// <summary>
		/// Opens the texture pack with the given file name.
		/// </summary>
		/// <param name="texturePackFile">Name of the texture pack file.</param>
		/// <param name="fallBackTexturePackFile">The fall back texture pack, when not given uses the default texture-pack.</param>
		/// <returns>
		/// a <see cref="DynamicTexturePack" /> to read texture pack information.
		/// </returns>
		/// <exception cref="System.IO.FileNotFoundException">Thrown when the texture pack file was not found</exception>
		public DynamicTexturePack OpenTexturePack( IFile texturePackFile, IFile fallBackTexturePackFile = null )
		{
			Contract.Requires<ArgumentNullException>( texturePackFile != null );
			Contract.Ensures( Contract.Result<DynamicTexturePack>() != null );

			return new DynamicTexturePack( texturePackFile, fallBackTexturePackFile );
		}

		/// <summary>
		/// Gets the default texture-pack path.
		/// </summary>
		/// <returns>The default texture-pack path</returns>
		public IFile GetDefaultTexturePackFile()
		{
			IFile texturePackFile =
				_rootGameFolder.GetFile( "bin", "minecraft.jar" );

			// when not found, try the latest version in the versions folder
			if( texturePackFile == null )
				texturePackFile = _rootGameFolder.GetFile( "versions", "1.5.2", "1.5.2.jar" );

			return texturePackFile;
		}

		/// <summary>
		/// Opens the default texture pack
		/// </summary>
		/// <returns>The default texture pack</returns>
		public DynamicTexturePack OpenDefaultTexturePack()
		{
			return new DynamicTexturePack( GetDefaultTexturePackFile() );
		}

		#endregion
		#region Resource-Pack

		public IObservable<IFile> GetResourcePackFilesAsync( ProfileConfigurationMetadata profile )
		{
			return TaskService.Default.Observe( () => GetResourcePackFiles( profile ) );
		}

		public IEnumerable<IFile> GetResourcePackFiles( ProfileConfigurationMetadata profile )
		{
			IFolder resourcePackFolder = profile.GameFolder.GetFolder( "resourcepacks" );

			if( resourcePackFolder == null )
				return new IFile[0];

			return
				resourcePackFolder
					.GetFiles()
					.Where( file => file.Name.EndsWith( ".zip" ) );
		}

		public IObservable<IFile> GetAllResourcePackFilesAsync()
		{
			return TaskService.Default.Observe( GetAllResourcePackFiles );
		}

		public IEnumerable<IFile> GetAllResourcePackFiles()
		{
			Contract.Ensures( Contract.Result<IEnumerable<IFile>>() != null );

			return
				GetProfileLocations()
					.Select( f => f.GetFolder( "resourcepacks" ) )
					.Where( f => f == null )
					.SelectMany( f => f.GetFiles().Where( file => file.Name.EndsWith( ".zip" ) ) );
		}

		public DynamicResourcePack OpenResourcePack()
		{
			throw new NotImplementedException();
		}

		public IFile GetDefaultResourcePackFile( ProfileConfigurationMetadata profile )
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Open the default resource pack for the given profile.
		/// </summary>
		/// <param name="profile">The profile.</param>
		/// <returns></returns>
		public DynamicResourcePack OpenDefaultResourcePack( ProfileConfigurationMetadata profile )
		{
			throw new NotImplementedException();
		}

		#endregion

		/// <summary>
		/// Returns the world names found in the saves folder.
		/// </summary>
		/// <returns>Sequence of all world names found in the saves folder.</returns>
		public IEnumerable<IFolder> GetWorldFolders()
		{
			Contract.Ensures( Contract.Result<IEnumerable<IFolder>>() != null );

			IFolder saveFolder = _rootGameFolder.GetFolder( "saves" );
			IEnumerable<IFolder> folders = saveFolder.GetFolders();

			return
				from folder in folders
				let levelDat = folder.GetFile( "level.dat" )
				where levelDat != null
				select folder;
		}

		/// <summary>
		/// Opens the level in the specified folder.
		/// </summary>
		/// <param name="worldFolder">Folder of the level to open.</param>
		/// <returns>a <see cref="DynamicWorld"/> to read game level information.</returns>
		public DynamicWorld OpenWorld( IFolder worldFolder )
		{
			Contract.Requires<ArgumentOutOfRangeException>( worldFolder != null );
			Contract.Ensures( Contract.Result<DynamicWorld>() != null );

			return new DynamicWorld( this, worldFolder );
		}
	}
}
